/***
 * Copyright (C) Microsoft. All rights reserved.
 * Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
 *
 * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
 *
 * Utilities
 *
 * For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk
 *
 * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
 ****/

#include "pch.h"

#include <algorithm>
#include "asyncrt_utils.h"
#include <stdexcept>
#include <string>
#include <time.h>

using namespace cljson;
using namespace clstring;
using namespace clstring::conversions;

namespace
{
	struct to_lower_ch_impl
	{
		char operator()(char c) const CPPREST_NOEXCEPT
		{
			if (c >= 'A' && c <= 'Z') return static_cast<char>(c - 'A' + 'a');
			return c;
		}

		wchar_t operator()(wchar_t c) const CPPREST_NOEXCEPT
		{
			if (c >= L'A' && c <= L'Z') return static_cast<wchar_t>(c - L'A' + L'a');
			return c;
		}
	};

	CPPREST_CONSTEXPR to_lower_ch_impl to_lower_ch{};

	struct eq_lower_ch_impl
	{
		template<class CharT>
		inline CharT operator()(const CharT left, const CharT right) const CPPREST_NOEXCEPT
		{
			return to_lower_ch(left) == to_lower_ch(right);
		}
	};

	CPPREST_CONSTEXPR eq_lower_ch_impl eq_lower_ch{};

	struct lt_lower_ch_impl
	{
		template<class CharT>
		inline CharT operator()(const CharT left, const CharT right) const CPPREST_NOEXCEPT
		{
			return to_lower_ch(left) < to_lower_ch(right);
		}
	};

	CPPREST_CONSTEXPR lt_lower_ch_impl lt_lower_ch{};
} // namespace

namespace clstring
{
	namespace details
	{
		_ASYNCRTIMP bool __cdecl str_iequal(const std::string& left, const std::string& right) CPPREST_NOEXCEPT
		{
			return left.size() == right.size() && std::equal(left.cbegin(), left.cend(), right.cbegin(), eq_lower_ch);
		}

		_ASYNCRTIMP bool __cdecl str_iequal(const std::wstring& left, const std::wstring& right) CPPREST_NOEXCEPT
		{
			return left.size() == right.size() && std::equal(left.cbegin(), left.cend(), right.cbegin(), eq_lower_ch);
		}

		_ASYNCRTIMP bool __cdecl str_iless(const std::string& left, const std::string& right) CPPREST_NOEXCEPT
		{
			return std::lexicographical_compare(left.cbegin(), left.cend(), right.cbegin(), right.cend(), lt_lower_ch);
		}

		_ASYNCRTIMP bool __cdecl str_iless(const std::wstring& left, const std::wstring& right) CPPREST_NOEXCEPT
		{
			return std::lexicographical_compare(left.cbegin(), left.cend(), right.cbegin(), right.cend(), lt_lower_ch);
		}

		_ASYNCRTIMP void __cdecl inplace_tolower(std::string& target) CPPREST_NOEXCEPT
		{
			for (auto& ch : target)
			{
				ch = to_lower_ch(ch);
			}
		}

		_ASYNCRTIMP void __cdecl inplace_tolower(std::wstring& target) CPPREST_NOEXCEPT
		{
			for (auto& ch : target)
			{
				ch = to_lower_ch(ch);
			}
		}

#if !defined(ANDROID) && !defined(__ANDROID__)
		std::once_flag g_c_localeFlag;
		std::unique_ptr<scoped_c_thread_locale::xplat_locale, void (*)(scoped_c_thread_locale::xplat_locale*)> g_c_locale(
			nullptr, [](scoped_c_thread_locale::xplat_locale*) {});
		scoped_c_thread_locale::xplat_locale scoped_c_thread_locale::c_locale()
		{
			std::call_once(g_c_localeFlag, [&]() {
				scoped_c_thread_locale::xplat_locale* clocale = new scoped_c_thread_locale::xplat_locale();
#ifdef _WIN32
				* clocale = _create_locale(LC_ALL, "C");
				if (clocale == nullptr || *clocale == nullptr)
				{
					throw std::runtime_error("Unable to create 'C' locale.");
				}
				auto deleter = [](scoped_c_thread_locale::xplat_locale* clocale) {
					_free_locale(*clocale);
					delete clocale;
				};
#else
				* clocale = newlocale(LC_ALL_MASK, "C", nullptr);
				if (clocale == nullptr || *clocale == nullptr)
				{
					throw std::runtime_error("Unable to create 'C' locale.");
				}
				auto deleter = [](scoped_c_thread_locale::xplat_locale* clocale)
				{
					freelocale(*clocale);
					delete clocale;
				};
#endif
				g_c_locale =
					std::unique_ptr<scoped_c_thread_locale::xplat_locale, void (*)(scoped_c_thread_locale::xplat_locale*)>(
						clocale, deleter);
				});
			return *g_c_locale;
		}
#endif

#ifdef _WIN32
		scoped_c_thread_locale::scoped_c_thread_locale() : m_prevLocale(), m_prevThreadSetting(-1)
		{
			char* prevLocale = setlocale(LC_ALL, nullptr);
			if (prevLocale == nullptr)
			{
				throw std::runtime_error("Unable to retrieve current locale.");
			}

			if (std::strcmp(prevLocale, "C") != 0)
			{
				m_prevLocale = prevLocale;
				m_prevThreadSetting = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
				if (m_prevThreadSetting == -1)
				{
					throw std::runtime_error("Unable to enable per thread locale.");
				}
				if (setlocale(LC_ALL, "C") == nullptr)
				{
					_configthreadlocale(m_prevThreadSetting);
					throw std::runtime_error("Unable to set locale");
				}
			}
		}

		scoped_c_thread_locale::~scoped_c_thread_locale()
		{
			if (m_prevThreadSetting != -1)
			{
				setlocale(LC_ALL, m_prevLocale.c_str());
				_configthreadlocale(m_prevThreadSetting);
			}
		}
#elif (defined(ANDROID) || defined(__ANDROID__))
		scoped_c_thread_locale::scoped_c_thread_locale() { }
		scoped_c_thread_locale::~scoped_c_thread_locale() { }
#else
		scoped_c_thread_locale::scoped_c_thread_locale() : m_prevLocale(nullptr)
		{
			char* prevLocale = setlocale(LC_ALL, nullptr);
			if (prevLocale == nullptr)
			{
				throw std::runtime_error("Unable to retrieve current locale.");
			}

			if (std::strcmp(prevLocale, "C") != 0)
			{
				m_prevLocale = uselocale(c_locale());
				if (m_prevLocale == nullptr)
				{
					throw std::runtime_error("Unable to set locale");
				}
			}
		}

		scoped_c_thread_locale::~scoped_c_thread_locale()
		{
			if (m_prevLocale != nullptr)
			{
				uselocale(m_prevLocale);
			}
		}
#endif
	} // namespace details

	namespace details
	{
		const std::error_category& __cdecl platform_category()
		{
#ifdef _WIN32
			return windows_category();
#else
			return linux_category();
#endif
		}

#ifdef _WIN32

		// Remove once VS 2013 is no longer supported.
#if _MSC_VER < 1900
		static details::windows_category_impl instance;
#endif
		const std::error_category& __cdecl windows_category()
		{
#if _MSC_VER >= 1900
			static details::windows_category_impl instance;
#endif
			return instance;
		}

		std::string windows_category_impl::message(int errorCode) const CPPREST_NOEXCEPT
		{
			const size_t buffer_size = 4096;
			DWORD dwFlags = FORMAT_MESSAGE_FROM_SYSTEM;
			LPCVOID lpSource = NULL;

#if !defined(__cplusplus_winrt)
			if (errorCode >= 12000)
			{
				dwFlags = FORMAT_MESSAGE_FROM_HMODULE;
				lpSource = GetModuleHandleA("winhttp.dll"); // this handle DOES NOT need to be freed
			}
#endif

			std::wstring buffer(buffer_size, 0);

			const auto result = ::FormatMessageW(dwFlags, lpSource, errorCode, 0, &buffer[0], buffer_size, NULL);

			if (result == 0)
			{
				return "Unable to get an error message for error code: " + std::to_string(errorCode) + ".";
			}

			// strip exceeding characters of the initial resize call
			buffer.resize(result);

			return clstring::conversions::to_utf8string(buffer);
		}

		std::error_condition windows_category_impl::default_error_condition(int errorCode) const CPPREST_NOEXCEPT
		{
			// First see if the STL implementation can handle the mapping for common cases.
			const std::error_condition errCondition = std::system_category().default_error_condition(errorCode);
			const std::string errConditionMsg = errCondition.message();
			if (!clstring::details::str_iequal(errConditionMsg, "unknown error"))
			{
				return errCondition;
			}

			switch (errorCode)
			{
#ifndef __cplusplus_winrt
			//case ERROR_WINHTTP_TIMEOUT: return std::errc::timed_out;
			//case ERROR_WINHTTP_CANNOT_CONNECT: return std::errc::host_unreachable;
			//case ERROR_WINHTTP_CONNECTION_ERROR: return std::errc::connection_aborted;
#endif
			case INET_E_RESOURCE_NOT_FOUND:
			case INET_E_CANNOT_CONNECT: return std::errc::host_unreachable;
			case INET_E_CONNECTION_TIMEOUT: return std::errc::timed_out;
			case INET_E_DOWNLOAD_FAILURE: return std::errc::connection_aborted;
			default: break;
			}

			return std::error_condition(errorCode, *this);
		}

#else

		const std::error_category& __cdecl linux_category()
		{
			// On Linux we are using boost error codes which have the exact same
			// mapping and are equivalent with std::generic_category error codes.
			return std::generic_category();
		}

#endif

	} // namespace details

#define LOW_3BITS 0x7
#define LOW_4BITS 0xF
#define LOW_5BITS 0x1F
#define LOW_6BITS 0x3F
#define BIT4 0x8
#define BIT5 0x10
#define BIT6 0x20
#define BIT7 0x40
#define BIT8 0x80
#define L_SURROGATE_START 0xDC00
#define L_SURROGATE_END 0xDFFF
#define H_SURROGATE_START 0xD800
#define H_SURROGATE_END 0xDBFF
#define SURROGATE_PAIR_START 0x10000

// Create a dedicated type for characters to avoid the issue
// of different platforms defaulting char to be either signed
// or unsigned.
	using UtilCharInternal_t = signed char;

	inline size_t count_utf8_to_utf16(const std::string& s)
	{
		const size_t sSize = s.size();
		auto const sData = reinterpret_cast<const UtilCharInternal_t*>(s.data());
		size_t result{ sSize };

		for (size_t index = 0; index < sSize;)
		{
			if (sData[index] >= 0)
			{
				// use fast inner loop to skip single byte code points (which are
				// expected to be the most frequent)
				while ((++index < sSize) && (sData[index] >= 0))
					;

				if (index >= sSize) break;
			}

			// start special handling for multi-byte code points
			const UtilCharInternal_t c{ sData[index++] };

			if ((c & BIT7) == 0)
			{
				throw std::range_error("UTF-8 string character can never start with 10xxxxxx");
			}
			else if ((c & BIT6) == 0) // 2 byte character, 0x80 to 0x7FF
			{
				if (index == sSize)
				{
					throw std::range_error("UTF-8 string is missing bytes in character");
				}

				const UtilCharInternal_t c2{ sData[index++] };
				if ((c2 & 0xC0) != BIT8)
				{
					throw std::range_error("UTF-8 continuation byte is missing leading bit mask");
				}

				// can't require surrogates for 7FF
				--result;
			}
			else if ((c & BIT5) == 0) // 3 byte character, 0x800 to 0xFFFF
			{
				if (sSize - index < 2)
				{
					throw std::range_error("UTF-8 string is missing bytes in character");
				}

				const UtilCharInternal_t c2{ sData[index++] };
				const UtilCharInternal_t c3{ sData[index++] };
				if (((c2 | c3) & 0xC0) != BIT8)
				{
					throw std::range_error("UTF-8 continuation byte is missing leading bit mask");
				}

				result -= 2;
			}
			else if ((c & BIT4) == 0) // 4 byte character, 0x10000 to 0x10FFFF
			{
				if (sSize - index < 3)
				{
					throw std::range_error("UTF-8 string is missing bytes in character");
				}

				const UtilCharInternal_t c2{ sData[index++] };
				const UtilCharInternal_t c3{ sData[index++] };
				const UtilCharInternal_t c4{ sData[index++] };
				if (((c2 | c3 | c4) & 0xC0) != BIT8)
				{
					throw std::range_error("UTF-8 continuation byte is missing leading bit mask");
				}

				const uint32_t codePoint =
					((c & LOW_3BITS) << 18) | ((c2 & LOW_6BITS) << 12) | ((c3 & LOW_6BITS) << 6) | (c4 & LOW_6BITS);
				result -= (3 - (codePoint >= SURROGATE_PAIR_START));
			}
			else
			{
				throw std::range_error("UTF-8 string has invalid Unicode code point");
			}
		}

		return result;
	}

	utf16string __cdecl conversions::utf8_to_utf16(const std::string& s)
	{
		// Save repeated heap allocations, use the length of resulting sequence.
		const size_t srcSize = s.size();
		auto const srcData = reinterpret_cast<const UtilCharInternal_t*>(s.data());
		utf16string dest(count_utf8_to_utf16(s), L'\0');
		utf16string::value_type* const destData = &dest[0];
		size_t destIndex = 0;

		for (size_t index = 0; index < srcSize; ++index)
		{
			UtilCharInternal_t src = srcData[index];
			switch (src & 0xF0)
			{
			case 0xF0: // 4 byte character, 0x10000 to 0x10FFFF
			{
				const UtilCharInternal_t c2{ srcData[++index] };
				const UtilCharInternal_t c3{ srcData[++index] };
				const UtilCharInternal_t c4{ srcData[++index] };
				uint32_t codePoint =
					((src & LOW_3BITS) << 18) | ((c2 & LOW_6BITS) << 12) | ((c3 & LOW_6BITS) << 6) | (c4 & LOW_6BITS);
				if (codePoint >= SURROGATE_PAIR_START)
				{
					// In UTF-16 U+10000 to U+10FFFF are represented as two 16-bit code units, surrogate pairs.
					//  - 0x10000 is subtracted from the code point
					//  - high surrogate is 0xD800 added to the top ten bits
					//  - low surrogate is 0xDC00 added to the low ten bits
					codePoint -= SURROGATE_PAIR_START;
					destData[destIndex++] = static_cast<utf16string::value_type>((codePoint >> 10) | H_SURROGATE_START);
					destData[destIndex++] =
						static_cast<utf16string::value_type>((codePoint & 0x3FF) | L_SURROGATE_START);
				}
				else
				{
					// In UTF-16 U+0000 to U+D7FF and U+E000 to U+FFFF are represented exactly as the Unicode code point
					// value. U+D800 to U+DFFF are not valid characters, for simplicity we assume they are not present
					// but will encode them if encountered.
					destData[destIndex++] = static_cast<utf16string::value_type>(codePoint);
				}
			}
			break;
			case 0xE0: // 3 byte character, 0x800 to 0xFFFF
			{
				const UtilCharInternal_t c2{ srcData[++index] };
				const UtilCharInternal_t c3{ srcData[++index] };
				destData[destIndex++] = static_cast<utf16string::value_type>(
					((src & LOW_4BITS) << 12) | ((c2 & LOW_6BITS) << 6) | (c3 & LOW_6BITS));
			}
			break;
			case 0xD0: // 2 byte character, 0x80 to 0x7FF
			case 0xC0:
			{
				const UtilCharInternal_t c2{ srcData[++index] };
				destData[destIndex++] =
					static_cast<utf16string::value_type>(((src & LOW_5BITS) << 6) | (c2 & LOW_6BITS));
			}
			break;
			default: // single byte character, 0x0 to 0x7F
				// try to use a fast inner loop for following single byte characters,
				// since they are quite probable
				do
				{
					destData[destIndex++] = static_cast<utf16string::value_type>(srcData[index++]);
				} while (index < srcSize && srcData[index] > 0);
				// adjust index since it will be incremented by the for loop
				--index;
			}
		}
		return dest;
	}

	inline size_t count_utf16_to_utf8(const utf16string& w)
	{
		const utf16string::value_type* const srcData = &w[0];
		const size_t srcSize = w.size();
		size_t destSize(srcSize);
		for (size_t index = 0; index < srcSize; ++index)
		{
			const utf16string::value_type ch(srcData[index]);
			if (ch <= 0x7FF)
			{
				if (ch > 0x7F) // 2 bytes needed (11 bits used)
				{
					++destSize;
				}
			}
			// Check for high surrogate.
			else if (ch >= H_SURROGATE_START && ch <= H_SURROGATE_END) // 4 bytes needed (21 bits used)
			{
				++index;
				if (index == srcSize)
				{
					throw std::range_error("UTF-16 string is missing low surrogate");
				}

				const auto lowSurrogate = srcData[index];
				if (lowSurrogate < L_SURROGATE_START || lowSurrogate > L_SURROGATE_END)
				{
					throw std::range_error("UTF-16 string has invalid low surrogate");
				}

				destSize += 2;
			}
			else // 3 bytes needed (16 bits used)
			{
				destSize += 2;
			}
		}

		return destSize;
	}

	std::string __cdecl conversions::utf16_to_utf8(const utf16string& w)
	{
		const size_t srcSize = w.size();
		const utf16string::value_type* const srcData = &w[0];
		std::string dest(count_utf16_to_utf8(w), '\0');
		std::string::value_type* const destData = &dest[0];
		size_t destIndex(0);

		for (size_t index = 0; index < srcSize; ++index)
		{
			const utf16string::value_type src = srcData[index];
			if (src <= 0x7FF)
			{
				if (src <= 0x7F) // single byte character
				{
					destData[destIndex++] = static_cast<char>(src);
				}
				else // 2 bytes needed (11 bits used)
				{
					destData[destIndex++] = static_cast<char>(char((src >> 6) | 0xC0));        // leading 5 bits
					destData[destIndex++] = static_cast<char>(char((src & LOW_6BITS) | BIT8)); // trailing 6 bits
				}
			}
			// Check for high surrogate.
			else if (src >= H_SURROGATE_START && src <= H_SURROGATE_END)
			{
				const auto highSurrogate = src;
				const auto lowSurrogate = srcData[++index];

				// To get from surrogate pair to Unicode code point:
				// - subtract 0xD800 from high surrogate, this forms top ten bits
				// - subtract 0xDC00 from low surrogate, this forms low ten bits
				// - add 0x10000
				// Leaves a code point in U+10000 to U+10FFFF range.
				uint32_t codePoint = highSurrogate - H_SURROGATE_START;
				codePoint <<= 10;
				codePoint |= lowSurrogate - L_SURROGATE_START;
				codePoint += SURROGATE_PAIR_START;

				// 4 bytes needed (21 bits used)
				destData[destIndex++] = static_cast<char>((codePoint >> 18) | 0xF0);               // leading 3 bits
				destData[destIndex++] = static_cast<char>(((codePoint >> 12) & LOW_6BITS) | BIT8); // next 6 bits
				destData[destIndex++] = static_cast<char>(((codePoint >> 6) & LOW_6BITS) | BIT8);  // next 6 bits
				destData[destIndex++] = static_cast<char>((codePoint & LOW_6BITS) | BIT8);         // trailing 6 bits
			}
			else // 3 bytes needed (16 bits used)
			{
				destData[destIndex++] = static_cast<char>((src >> 12) | 0xE0);              // leading 4 bits
				destData[destIndex++] = static_cast<char>(((src >> 6) & LOW_6BITS) | BIT8); // middle 6 bits
				destData[destIndex++] = static_cast<char>((src & LOW_6BITS) | BIT8);        // trailing 6 bits
			}
		}

		return dest;
	}

	utf16string __cdecl conversions::usascii_to_utf16(const std::string& s)
	{
		// Ascii is a subset of UTF-8 so just convert to UTF-16
		return utf8_to_utf16(s);
	}

	utf16string __cdecl conversions::latin1_to_utf16(const std::string& s)
	{
		// Latin1 is the first 256 code points in Unicode.
		// In UTF-16 encoding each of these is represented as exactly the numeric code point.
		utf16string dest;
		// Prefer resize combined with for-loop over constructor dest(s.begin(), s.end())
		// for faster assignment.
		dest.resize(s.size());
		for (size_t i = 0; i < s.size(); ++i)
		{
			dest[i] = utf16char(static_cast<unsigned char>(s[i]));
		}
		return dest;
	}

	utf8string __cdecl conversions::latin1_to_utf8(const std::string& s) { return utf16_to_utf8(latin1_to_utf16(s)); }

#ifndef _UTF16_STRINGS
	clstring::string_t __cdecl conversions::to_string_t(utf16string&& s) { return utf16_to_utf8(std::move(s)); }
#endif

#ifdef _UTF16_STRINGS
	clstring::string_t __cdecl conversions::to_string_t(std::string&& s) { return utf8_to_utf16(std::move(s)); }
#endif

#ifndef _UTF16_STRINGS
	clstring::string_t __cdecl conversions::to_string_t(const utf16string& s) { return utf16_to_utf8(s); }
#endif

#ifdef _UTF16_STRINGS
	clstring::string_t __cdecl conversions::to_string_t(const std::string& s) { return utf8_to_utf16(s); }
#endif

	std::string __cdecl conversions::to_utf8string(const utf16string& value) { return utf16_to_utf8(value); }

	utf16string __cdecl conversions::to_utf16string(const std::string& value) { return utf8_to_utf16(value); }

	static const int64_t NtToUnixOffsetSeconds = 11644473600; // diff between windows and unix epochs (seconds)

	static bool year_is_leap_year_1601(int yearsSince1601)
	{
		int decimalYear = yearsSince1601 + 1601;
		return (decimalYear % 4 == 0 && (decimalYear % 100 != 0 || decimalYear % 400 == 0));
	}

	static const int SecondsInMinute = 60;
	static const int SecondsInHour = SecondsInMinute * 60;
	static const int SecondsInDay = SecondsInHour * 24;

	static const int DaysInYear = 365;
	static const int DaysIn4Years = DaysInYear * 4 + 1;
	static const int DaysIn100Years = DaysIn4Years * 25 - 1;
	static const int DaysIn400Years = DaysIn100Years * 4 + 1;

	static const int SecondsInYear = SecondsInDay * DaysInYear;
	static const int SecondsIn4Years = SecondsInDay * DaysIn4Years;
	static const int64_t SecondsIn100Years = static_cast<int64_t>(SecondsInDay) * DaysIn100Years;
	static const int64_t SecondsIn400Years = static_cast<int64_t>(SecondsInDay) * DaysIn400Years;

	static int count_leap_years_1601(int yearsSince1601)
	{
		int year400 = yearsSince1601 / 400;
		yearsSince1601 -= year400 * 400;
		int result = year400 * 97;

		int year100 = yearsSince1601 / 100;
		yearsSince1601 -= year100 * 100;
		result += year100 * 24;

		result += yearsSince1601 / 4;

		return result;
	}

	// The following table assumes no leap year; leap year is added separately
	static const unsigned short cumulative_days_to_month[12] = {
		0,   // Jan
		31,  // Feb
		59,  // Mar
		90,  // Apr
		120, // May
		151, // Jun
		181, // Jul
		212, // Aug
		243, // Sep
		273, // Oct
		304, // Nov
		334  // Dec
	};

	static const unsigned short cumulative_days_to_month_leap[12] = {
		0,   // Jan
		31,  // Feb
		60,  // Mar
		91,  // Apr
		121, // May
		152, // Jun
		182, // Jul
		213, // Aug
		244, // Sep
		274, // Oct
		305, // Nov
		335  // Dec
	};

	datetime __cdecl datetime::utc_now()
	{
#ifdef _WIN32
		ULARGE_INTEGER largeInt;
		FILETIME fileTime;
		GetSystemTimeAsFileTime(&fileTime);

		largeInt.LowPart = fileTime.dwLowDateTime;
		largeInt.HighPart = fileTime.dwHighDateTime;

		return datetime(largeInt.QuadPart);
#else // LINUX
		struct timeval time;
		gettimeofday(&time, nullptr);
		int64_t result = NtToUnixOffsetSeconds + time.tv_sec;
		result *= _secondTicks;      // convert to 10e-7
		result += time.tv_usec * 10; // convert and add microseconds, 10e-6 to 10e-7
		return datetime(static_cast<interval_type>(result));
#endif
	}

	static const char dayNames[] = "Sun\0Mon\0Tue\0Wed\0Thu\0Fri\0Sat";
	static const char monthNames[] = "Jan\0Feb\0Mar\0Apr\0May\0Jun\0Jul\0Aug\0Sep\0Oct\0Nov\0Dec";

	struct compute_year_result
	{
		int year;
		int secondsLeftThisYear;
	};

	static compute_year_result compute_year_1601(int64_t secondsSince1601)
	{
		int year400 = static_cast<int>(secondsSince1601 / SecondsIn400Years);
		secondsSince1601 -= year400 * SecondsIn400Years;

		int year100 = static_cast<int>(secondsSince1601 / SecondsIn100Years);
		secondsSince1601 -= year100 * SecondsIn100Years;

		int year4 = static_cast<int>(secondsSince1601 / SecondsIn4Years);
		int secondsInt = static_cast<int>(secondsSince1601 - year4 * SecondsIn4Years);

		int year1 = secondsInt / SecondsInYear;
		if (year1 == 4)
		{
			// this is the last day in a leap year
			year1 = 3;
		}

		secondsInt -= year1 * SecondsInYear;
		return { year400 * 400 + year100 * 100 + year4 * 4 + year1, secondsInt };
	}

	// The constant below was calculated by running the following test program on a Windows machine:
	// #include <windows.h>
	// #include <stdio.h>

	// int main() {
	//     SYSTEMTIME st;
	//     st.wYear = 9999;
	//     st.wMonth = 12;
	//     st.wDayOfWeek = 5;
	//     st.wDay = 31;
	//     st.wHour = 23;
	//     st.wMinute = 59;
	//     st.wSecond = 59;
	//     st.wMilliseconds = 999;

	//     unsigned long long ft;
	//     if (SystemTimeToFileTime(&st, reinterpret_cast<FILETIME*>(&ft))) {
	//         printf("0x%016llX\n", ft);
	//     } else {
	//         puts("failed!");
	//     }
	// }

	clstring::string_t datetime::to_string(date_format format) const
	{
		const int64_t interval = static_cast<int64_t>(m_interval);
		if (interval > INT64_C(0x24C85A5ED1C018F0))
		{
			throw std::out_of_range("The requested year exceeds the year 9999.");
		}

		const int64_t secondsSince1601 = interval / _secondTicks; // convert to seconds
		const int fracSec = static_cast<int>(interval % _secondTicks);

		const auto yearData = compute_year_1601(secondsSince1601);
		const int year = yearData.year;
		const int yearDay = yearData.secondsLeftThisYear / SecondsInDay;
		int leftover = yearData.secondsLeftThisYear % SecondsInDay;
		const int hour = leftover / SecondsInHour;
		leftover = leftover % SecondsInHour;
		const int minute = leftover / SecondsInMinute;
		leftover = leftover % SecondsInMinute;

		const auto& monthTable = year_is_leap_year_1601(year) ? cumulative_days_to_month_leap : cumulative_days_to_month;
		int month = 0;
		while (month < 11 && monthTable[month + 1] <= yearDay)
		{
			++month;
		}

		const auto monthDay = yearDay - monthTable[month] + 1;
		const auto weekday = static_cast<int>((secondsSince1601 / SecondsInDay + 1) % 7);

		char outBuffer[38]; // Thu, 01 Jan 1970 00:00:00 GMT\0
		// 1970-01-01T00:00:00.1234567Z\0
		char* outCursor = outBuffer;
		switch (format)
		{
		case RFC_1123:
#ifdef _MSC_VER
			sprintf_s(outCursor,
				26,
				"%s, %02d %s %04d %02d:%02d:%02d",
				dayNames + 4 * weekday,
				monthDay,
				monthNames + 4 * month,
				year + 1601,
				hour,
				minute,
				leftover);
#else  // ^^^ _MSC_VER // !_MSC_VER vvv
			sprintf(outCursor,
				"%s, %02d %s %04d %02d:%02d:%02d",
				dayNames + 4 * weekday,
				monthDay,
				monthNames + 4 * month,
				year + 1601,
				hour,
				minute,
				leftover);
#endif // _MSC_VER
			outCursor += 25;
			memcpy(outCursor, " GMT", 4);
			outCursor += 4;
			return clstring::string_t(outBuffer, outCursor);
		case ISO_8601:
#ifdef _MSC_VER
			sprintf_s(outCursor,
				20,
				"%04d-%02d-%02dT%02d:%02d:%02d",
				year + 1601,
				month + 1,
				monthDay,
				hour,
				minute,
				leftover);
#else  // ^^^ _MSC_VER // !_MSC_VER vvv
			sprintf(
				outCursor, "%04d-%02d-%02dT%02d:%02d:%02d", year + 1601, month + 1, monthDay, hour, minute, leftover);
#endif // _MSC_VER
			outCursor += 19;
			if (fracSec != 0)
			{
				// Append fractional second, which is a 7-digit value with no trailing zeros
				// This way, '1200' becomes '00012'
#ifdef _MSC_VER
				size_t appended = sprintf_s(outCursor, 9, ".%07d", fracSec);
#else  // ^^^ _MSC_VER // !_MSC_VER vvv
				size_t appended = sprintf(outCursor, ".%07d", fracSec);
#endif // _MSC_VER
				while (outCursor[appended - 1] == '0')
				{
					--appended; // trim trailing zeros
				}

				outCursor += appended;
			}

			*outCursor = 'Z';
			++outCursor;
			return clstring::string_t(outBuffer, outCursor);
		default: throw std::invalid_argument("Unrecognized date format.");
		}
	}

	template<class CharT>
	static bool string_starts_with(const CharT* haystack, const char* needle)
	{
		while (*needle)
		{
			if (*haystack != static_cast<CharT>(*needle))
			{
				return false;
			}

			++haystack;
			++needle;
		}

		return true;
	}

#define ascii_isdigit(c) ((unsigned char)((unsigned char)(c) - '0') <= 9)
#define ascii_isdigit6(c) ((unsigned char)((unsigned char)(c) - '0') <= 6)
#define ascii_isdigit5(c) ((unsigned char)((unsigned char)(c) - '0') <= 5)
#define ascii_isdigit3(c) ((unsigned char)((unsigned char)(c) - '0') <= 3)
#define ascii_isdigit2(c) ((unsigned char)((unsigned char)(c) - '0') <= 2)
#define ascii_isdigit1(c) ((unsigned char)((unsigned char)(c) - '0') <= 1)

	static const unsigned char max_days_in_month[12] = {
		31, // Jan
		00, // Feb, special handling for leap years
		31, // Mar
		30, // Apr
		31, // May
		30, // Jun
		31, // Jul
		31, // Aug
		30, // Sep
		31, // Oct
		30, // Nov
		31  // Dec
	};

	static bool validate_day_month_1601(int day, int month, int year)
	{
		int maxDaysThisMonth;
		if (month == 1)
		{ // Feb needs leap year testing
			maxDaysThisMonth = 28 + year_is_leap_year_1601(year);
		}
		else
		{
			maxDaysThisMonth = max_days_in_month[month];
		}

		return day >= 1 && day <= maxDaysThisMonth;
	}

	static int get_year_day_1601(int month, int monthDay, int year)
	{
		return cumulative_days_to_month[month] + monthDay + (year_is_leap_year_1601(year) && month > 1) - 1;
	}

	template<class CharT>
	static int atoi2(const CharT* str)
	{
		return (static_cast<unsigned char>(str[0]) - '0') * 10 + (static_cast<unsigned char>(str[1]) - '0');
	}

	static int64_t timezone_adjust(int64_t result, unsigned char chSign, int adjustHours, int adjustMinutes)
	{
		if (adjustHours > 23)
		{
			return -1;
		}

		// adjustMinutes > 59 is impossible due to digit 5 check
		const int tzAdjust = adjustMinutes * 60 + adjustHours * 60 * 60;
		if (chSign == '-')
		{
			if (INT64_MAX - result < tzAdjust)
			{
				return -1;
			}

			result += tzAdjust;
		}
		else
		{
			if (tzAdjust > result)
			{
				return -1;
			}

			result -= tzAdjust;
		}

		return result;
	}

	/*
	https://tools.ietf.org/html/rfc822
	https://tools.ietf.org/html/rfc1123

	date-time   =  [ day "," ] date time        ; dd mm yy
												;  hh:mm:ss zzz

	day         =  "Mon"  / "Tue" /  "Wed"  / "Thu"
				/  "Fri"  / "Sat" /  "Sun"

	date        =  1*2DIGIT month 2DIGIT        ; day month year
												;  e.g. 20 Jun 82
	RFC1123 changes this to:
	date        =  1*2DIGIT month 2*4DIGIT        ; day month year
												  ;  e.g. 20 Jun 1982
	This implementation only accepts 4 digit years.

	month       =  "Jan"  /  "Feb" /  "Mar"  /  "Apr"
				/  "May"  /  "Jun" /  "Jul"  /  "Aug"
				/  "Sep"  /  "Oct" /  "Nov"  /  "Dec"

	time        =  hour zone                    ; ANSI and Military

	hour        =  2DIGIT ":" 2DIGIT [":" 2DIGIT]
												; 00:00:00 - 23:59:59

	zone        =  "UT"  / "GMT"                ; Universal Time
												; North American : UT
				/  "EST" / "EDT"                ;  Eastern:  - 5/ - 4
				/  "CST" / "CDT"                ;  Central:  - 6/ - 5
				/  "MST" / "MDT"                ;  Mountain: - 7/ - 6
				/  "PST" / "PDT"                ;  Pacific:  - 8/ - 7

	// military time deleted by RFC 1123

				/ ( ("+" / "-") 4DIGIT )        ; Local differential
												;  hours+min. (HHMM)
	*/

	datetime __cdecl datetime::from_string(const clstring::string_t& dateString, date_format format)
	{
		auto result = from_string_maximum_error(dateString, format);
		if (result == datetime::maximum())
		{
			return datetime();
		}

		return result;
	}

	datetime __cdecl datetime::from_string_maximum_error(const clstring::string_t& dateString, date_format format)
	{
		datetime result = datetime::maximum();
		int64_t secondsSince1601;
		uint64_t fracSec = 0;
		auto str = dateString.c_str();
		if (format == RFC_1123)
		{
			int parsedWeekday = 0;
			for (; parsedWeekday < 7; ++parsedWeekday)
			{
				if (string_starts_with(str, dayNames + parsedWeekday * 4) && str[3] == _XPLATSTR(',') &&
					str[4] == _XPLATSTR(' '))
				{
					str += 5; // parsed day of week
					break;
				}
			}

			int monthDay;
			if (ascii_isdigit3(str[0]) && ascii_isdigit(str[1]) && str[2] == _XPLATSTR(' '))
			{
				monthDay = atoi2(str); // validity checked later
				str += 3;              // parsed day
			}
			else if (ascii_isdigit(str[0]) && str[1] == _XPLATSTR(' '))
			{
				monthDay = str[0] - _XPLATSTR('0');
				str += 2; // parsed day
			}
			else
			{
				return result;
			}

			if (monthDay == 0)
			{
				return result;
			}

			int month = 0;
			for (;;)
			{
				if (string_starts_with(str, monthNames + month * 4))
				{
					break;
				}

				++month;
				if (month == 12)
				{
					return result;
				}
			}

			if (str[3] != _XPLATSTR(' '))
			{
				return result;
			}

			str += 4; // parsed month

			if (!ascii_isdigit(str[0]) || !ascii_isdigit(str[1]) || !ascii_isdigit(str[2]) || !ascii_isdigit(str[3]) ||
				str[4] != ' ')
			{
				return result;
			}

			int year = (str[0] - _XPLATSTR('0')) * 1000 + (str[1] - _XPLATSTR('0')) * 100 + (str[2] - _XPLATSTR('0')) * 10 +
				(str[3] - _XPLATSTR('0'));
			if (year < 1601)
			{
				return result;
			}

			year -= 1601;

			// days in month validity check
			if (!validate_day_month_1601(monthDay, month, year))
			{
				return result;
			}

			str += 5; // parsed year
			const int yearDay = get_year_day_1601(month, monthDay, year);

			if (!ascii_isdigit2(str[0]) || !ascii_isdigit(str[1]) || str[2] != _XPLATSTR(':') || !ascii_isdigit5(str[3]) ||
				!ascii_isdigit(str[4]))
			{
				return result;
			}

			const int hour = atoi2(str);
			if (hour > 23)
			{
				return result;
			}

			str += 3; // parsed hour
			const int minute = atoi2(str);
			str += 2; // parsed mins

			int sec;
			if (str[0] == ':')
			{
				if (!ascii_isdigit6(str[1]) || !ascii_isdigit(str[2]) || str[3] != _XPLATSTR(' '))
				{
					return result;
				}

				sec = atoi2(str + 1);
				str += 4; // parsed seconds
			}
			else if (str[0] == _XPLATSTR(' '))
			{
				sec = 0;
				str += 1; // parsed seconds
			}
			else
			{
				return result;
			}

			if (sec > 60)
			{ // 60 to allow leap seconds
				return result;
			}

			int daysSince1601 = year * DaysInYear + count_leap_years_1601(year) + yearDay;

			if (parsedWeekday != 7)
			{
				const int actualWeekday = (daysSince1601 + 1) % 7;

				if (parsedWeekday != actualWeekday)
				{
					return result;
				}
			}

			secondsSince1601 =
				static_cast<int64_t>(daysSince1601) * SecondsInDay + hour * SecondsInHour + minute * SecondsInMinute + sec;

			if (!string_starts_with(str, "GMT") && !string_starts_with(str, "UT"))
			{
				// some timezone adjustment necessary
				auto tzCh = _XPLATSTR('-');
				int tzHours;
				int tzMinutes = 0;
				if (string_starts_with(str, "EDT"))
				{
					tzHours = 4;
				}
				else if (string_starts_with(str, "EST") || string_starts_with(str, "CDT"))
				{
					tzHours = 5;
				}
				else if (string_starts_with(str, "CST") || string_starts_with(str, "MDT"))
				{
					tzHours = 6;
				}
				else if (string_starts_with(str, "MST") || string_starts_with(str, "PDT"))
				{
					tzHours = 7;
				}
				else if (string_starts_with(str, "PST"))
				{
					tzHours = 8;
				}
				else if ((str[0] == _XPLATSTR('+') || str[0] == _XPLATSTR('-')) && ascii_isdigit2(str[1]) &&
					ascii_isdigit(str[2]) && ascii_isdigit5(str[3]) && ascii_isdigit(str[4]))
				{
					tzCh = str[0];
					tzHours = atoi2(str + 1);
					tzMinutes = atoi2(str + 3);
				}
				else
				{
					return result;
				}

				secondsSince1601 = timezone_adjust(secondsSince1601, static_cast<unsigned char>(tzCh), tzHours, tzMinutes);
				if (secondsSince1601 < 0)
				{
					return result;
				}
			}
		}
		else if (format == ISO_8601)
		{
			// parse year
			if (!ascii_isdigit(str[0]) || !ascii_isdigit(str[1]) || !ascii_isdigit(str[2]) || !ascii_isdigit(str[3]))
			{
				return result;
			}

			int year = (str[0] - _XPLATSTR('0')) * 1000 + (str[1] - _XPLATSTR('0')) * 100 + (str[2] - _XPLATSTR('0')) * 10 +
				(str[3] - _XPLATSTR('0'));
			if (year < 1601)
			{
				return result;
			}

			year -= 1601;

			str += 4;
			if (*str == _XPLATSTR('-'))
			{
				++str;
			}

			// parse month
			if (!ascii_isdigit1(str[0]) || !ascii_isdigit(str[1]))
			{
				return result;
			}

			int month = atoi2(str);
			if (month < 1 || month > 12)
			{
				return result;
			}

			month -= 1;
			str += 2;

			if (*str == _XPLATSTR('-'))
			{
				++str;
			}

			// parse day
			if (!ascii_isdigit3(str[0]) || !ascii_isdigit(str[1]))
			{
				return result;
			}

			int monthDay = atoi2(str);
			if (!validate_day_month_1601(monthDay, month, year))
			{
				return result;
			}

			const int yearDay = get_year_day_1601(month, monthDay, year);

			str += 2;
			int daysSince1601 = year * DaysInYear + count_leap_years_1601(year) + yearDay;

			if (str[0] != _XPLATSTR('T') && str[0] != _XPLATSTR('t'))
			{
				// No time
				secondsSince1601 = static_cast<int64_t>(daysSince1601) * SecondsInDay;

				result.m_interval = static_cast<interval_type>(secondsSince1601 * _secondTicks + fracSec);
				return result;
			}

			++str; // skip 'T'

			// parse hour
			if (!ascii_isdigit2(str[0]) || !ascii_isdigit(str[1]))
			{
				return result;
			}

			const int hour = atoi2(str);
			str += 2;
			if (hour > 23)
			{
				return result;
			}

			if (*str == _XPLATSTR(':'))
			{
				++str;
			}

			// parse minute
			if (!ascii_isdigit5(str[0]) || !ascii_isdigit(str[1]))
			{
				return result;
			}

			const int minute = atoi2(str);
			// minute > 59 is impossible because we checked that the first digit is <= 5 in the basic format
			// check above

			str += 2;

			if (*str == _XPLATSTR(':'))
			{
				++str;
			}

			// parse seconds
			if (!ascii_isdigit6(str[0]) || !ascii_isdigit(str[1]))
			{
				return result;
			}

			const int sec = atoi2(str);
			// We allow 60 to account for leap seconds
			if (sec > 60)
			{
				return result;
			}

			str += 2;
			if (str[0] == _XPLATSTR('.') && ascii_isdigit(str[1]))
			{
				++str;
				int digits = 7;
				for (;;)
				{
					fracSec *= 10;
					fracSec += *str - _XPLATSTR('0');
					--digits;
					++str;
					if (digits == 0)
					{
						while (ascii_isdigit(*str))
						{
							// consume remaining fractional second digits we can't use
							++str;
						}

						break;
					}

					if (!ascii_isdigit(*str))
					{
						// no more digits in the input, do the remaining multiplies we need
						for (; digits != 0; --digits)
						{
							fracSec *= 10;
						}

						break;
					}
				}
			}

			secondsSince1601 =
				static_cast<int64_t>(daysSince1601) * SecondsInDay + hour * SecondsInHour + minute * SecondsInMinute + sec;

			if (str[0] == _XPLATSTR('Z') || str[0] == _XPLATSTR('z'))
			{
				// no adjustment needed for zulu time
			}
			else if (str[0] == _XPLATSTR('+') || str[0] == _XPLATSTR('-'))
			{
				const unsigned char offsetDirection = static_cast<unsigned char>(str[0]);
				if (!ascii_isdigit2(str[1]) || !ascii_isdigit(str[2]) || str[3] != _XPLATSTR(':') ||
					!ascii_isdigit5(str[4]) || !ascii_isdigit(str[5]))
				{
					return result;
				}

				secondsSince1601 = timezone_adjust(secondsSince1601, offsetDirection, atoi2(str + 1), atoi2(str + 4));
				if (secondsSince1601 < 0)
				{
					return result;
				}
			}
			else
			{
				// the timezone is malformed, but cpprestsdk currently accepts this as no timezone
			}
		}
		else
		{
			throw std::invalid_argument("unrecognized date format");
		}

		result.m_interval = static_cast<interval_type>(secondsSince1601 * _secondTicks + fracSec);
		return result;
	}

	/// <summary>
	/// Converts a timespan/interval in seconds to xml duration string as specified by
	/// http://www.w3.org/TR/xmlschema-2/#duration
	/// </summary>
	clstring::string_t __cdecl timespan::seconds_to_xml_duration(clstring::seconds durationSecs)
	{
		auto numSecs = durationSecs.count();

		// Find the number of minutes
		auto numMins = numSecs / 60;
		if (numMins > 0)
		{
			numSecs = numSecs % 60;
		}

		// Hours
		auto numHours = numMins / 60;
		if (numHours > 0)
		{
			numMins = numMins % 60;
		}

		// Days
		auto numDays = numHours / 24;
		if (numDays > 0)
		{
			numHours = numHours % 24;
		}

		// The format is:
		// PdaysDThoursHminutesMsecondsS
		clstring::string_t result;
		// (approximate mins/hours/secs as 2 digits each + 1 prefix character) + 1 for P prefix + 1 for T
		size_t baseReserveSize = ((numHours > 0) + (numMins > 0) + (numSecs > 0)) * 3 + 1;
		if (numDays > 0)
		{
			clstring::string_t daysStr = clstring::conversions::details::to_string_t(numDays);
			result.reserve(baseReserveSize + daysStr.size() + 1);
			result += _XPLATSTR('P');
			result += daysStr;
			result += _XPLATSTR('D');
		}
		else
		{
			result.reserve(baseReserveSize);
			result += _XPLATSTR('P');
		}

		result += _XPLATSTR('T');

		if (numHours > 0)
		{
			result += clstring::conversions::details::to_string_t(numHours);
			result += _XPLATSTR('H');
		}

		if (numMins > 0)
		{
			result += clstring::conversions::details::to_string_t(numMins);
			result += _XPLATSTR('M');
		}

		if (numSecs > 0)
		{
			result += clstring::conversions::details::to_string_t(numSecs);
			result += _XPLATSTR('S');
		}

		return result;
	}

	clstring::seconds __cdecl timespan::xml_duration_to_seconds(const clstring::string_t& timespanString)
	{
		// The format is:
		// PnDTnHnMnS
		// if n == 0 then the field could be omitted
		// The final S could be omitted

		int64_t numSecs = 0;
		auto cursor = timespanString.c_str();
		auto c = *cursor++; // skip 'P'
		while (c)
		{
			int val = 0;
			c = *cursor++;

			while (ascii_isdigit(c))
			{
				val = val * 10 + (c - _XPLATSTR('0'));
				c = *cursor++;

				if (c == _XPLATSTR('.'))
				{
					// decimal point is not handled
					do
					{
						c = *cursor++;
					} while (ascii_isdigit(c));
				}
			}

			if (c == _XPLATSTR('D')) numSecs += val * 24 * 3600; // days
			if (c == _XPLATSTR('H')) numSecs += val * 3600;      // Hours
			if (c == _XPLATSTR('M')) numSecs += val * 60;        // Minutes
			if (c == _XPLATSTR('S') || c == _XPLATSTR('\0'))
			{
				numSecs += val; // seconds
				break;
			}
		}

		return clstring::seconds(numSecs);
	}

	static const char c_allowed_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
	static const int chars_count = static_cast<int>(sizeof(c_allowed_chars) - 1);

	clstring::string_t nonce_generator::generate()
	{
		std::uniform_int_distribution<> distr(0, chars_count - 1);
		clstring::string_t result;
		result.reserve(length());
		std::generate_n(std::back_inserter(result), length(), [&] {
			return static_cast<clstring::char_t>(c_allowed_chars[distr(m_random)]);
			});
		return result;
	}

} // namespace utility
